module("svg", package.seeall)
require"png"
require"image_map"

STRATA_ORDER = {
	BACKGROUND = 1,
	LOW = 2,
	MEDIUM = 3,
	HIGH = 4,
	DIALOG = 5,
	FULLSCREEEN = 6,
	FULLSCREEN_DIALOG = 7,
	TOOLTIP = 8,
}

DRAWLAYER_ORDER = {
	BACKGROUND = 1,
	BORDER = 2,
	ARTWORK = 3,
	OVERLAY = 4,
	HIGHLIGHT = 5,
}

HEADER_P = [[
<?xml version="1.0" encoding="UTF-8"?>
<svg
		xmlns="http://www.w3.org/2000/svg"
		xmlns:svg="http://www.w3.org/2000/svg"
		xmlns:xlink="http://www.w3.org/1999/xlink"
		width="%s" height="%s"
>
	<style type="text/css">
		@font-face {
			font-family: 'Friz Quadrata TT';
			font-weight: normal;
			font-style: normal;
			src: url("Fonts/FRIZQT__.svg#FrizQT") format(svg)
		}
		.texture {
			fill-opacity: 0.8;
			fill: white;
		}
		.frame {
			fill-opacity: 0.1;
			fill: blue;
		}
		.backdrop {
			fill-opacity: 0.5;
			fill: black;
		}
		.protected-frame {
			fill-opacity: 0.1;
			fill: red;
		}
		#background {
			fill-opacity: 1;
			fill: white;
		}
	</style>
	<rect x="0" y="0" width="%d" height="%d" id="background" />
]]

FOOTER_P = [=[
	<text pointer-events="none" id="overlay-content" fill="red" font-size="28" text-anchor="middle" x="%d" y="%d" />
<script type="text/ecmascript"><![CDATA[
function showinfo(strcontent) {
	var content = document.getElementById("overlay-content");

	if (content.firstChild) {
		content.replaceChild(document.createTextNode(strcontent), content.firstChild);
	} else {
		content.appendChild(document.createTextNode(strcontent));
	}
}
]]></script>
</svg>
]=]

CLIP_PATH_P = [[<clipPath id="cp_%s"><rect %s /></clipPath>]]
MASK_P = [[<mask id="m_%s"><image xlink:href="%s" %s /></mask>]]
FRAME_HEADER_P = [[<g %s><rect %s class="%s" />]]
FRAME_FOOTER = [[</g>]]
DIMENSION_P = [[x="%d" y="%d" width="%d" height="%d"]]
MASKED_TEXTURE_P = [[<image preserveAspectRatio="none" clip-path="url(#cp_%s)" mask="url(#m_%s)" %s xlink:href="%s" />]]
TEXTURE_P = [[<image preserveAspectRatio="none" clip-path="url(#cp_%s)" %s xlink:href="%s" />]]
RECT_P = [[<rect %s class="texture" />]]
FONTSTRING_P = [[<text %s style="font-family:'%s' font-size:'%spx'" %s">%s</text>]]
COLOR_P = [[fill="currentColor" color="rgb(%d, %d, %d)]]
FILTER_P = [[<filter id="%s">%s</filter>]]
BACKDROP_FILTER_P = [[
	<feImage xlink:href="%s" width="%d" height="%d" />
	<feColorMatrix values="%s 0 0 0 0 0 %s 0 0 0 0 0 %s 0 0 0 0 0 %s 0" />
	<feTile />
	<feComposite %s in2="BackgroundImage" operator="arithmetic" k1="0" k2="1" k3="1" k4="0" />
]]
BACKDROP_P = [[<rect filter="url(#%s_bg)" %s />]]

function encode(s)
	if s and type(s) ~= "string" then
		s = tostring(s)
	end
	if not s then return end
	return s:replace("&", "&amp;"):replace("<", "&lt;"):replace('"', '&quot;'):replace("\\", "\\\\")
end

function encode_texture(s)
	if not s then return "" end
	s = s:replace("\\", "/")
	s = image_map[s]
	return encode(s)
end

function sort_regions(a, b)
	local la = assert(DRAWLAYER_ORDER[a[1]], "Unknown draw layer "..a[1])
	local lb = assert(DRAWLAYER_ORDER[b[1]], "Unknown draw layer "..b[1])
	if la == lb then
		return a[2] < b[2]
	else
		return la < lb
	end
end

function sort_frames(a, b)
	local sa = assert(STRATA_ORDER[a[1]], "Unknown strata "..a[1])
	local sb = assert(STRATA_ORDER[b[1]], "Unknown strata "..b[1])
	if sa == sb then
		return a[2] < b[2]
	else
		return sa < sb
	end
end

function get_dimension(f, base_height, accept_zero)
	local width, height, left = f:GetWidth(), f:GetHeight(), f:GetLeft()
	if not accept_zero and (width == 0 or height == 0) then return end
	local top = base_height - (f:GetBottom() + height)
	return left, top, width, height
end

local function get_color(f)
	local r, g, b = f:GetTextColor()
	return COLOR_P:format(r * 255, g * 255, b * 255)
end

local function get_texture(region)
	local texture = region:GetTexture()
	if texture and texture:find("^UNIT:") then texture = "Interface\\CHARACTERFRAME\\TemporaryPortrait-Male-Human" end
	if not texture or texture:find(":", nil, true) then return end
	return encode_texture(texture..".png")
end

local Svg = {
	__index = {
		open = function (self, file)
			self.file = file or "frames.svg"
			self.frames = {}
			self.regions = {}
			self.paths = {}
			self.masks = {}
			self.filters = {}
			self.fonts = {
				["Fonts\\FRIZQT__.TTF"] = "Friz Quadrata TT",
				["Fonts\ARIALN.TTF"] = "Arial Narrow",
			}
			self.index = 0
		end,
		encode_font = function (self, fontname)
			local font = self.fonts[fontname]
			if not font then
				log("WW Unknown Font %s", fontname)
				return fontname
			end
			return font
		end,
		write_defs = function (self, o)
			o:write("<defs>")
			for id, data in pairs(self.paths) do
				o:write(CLIP_PATH_P:format(id, DIMENSION_P:format(unpack(data))))
			end
			for id, data in pairs(self.masks) do
				o:write(MASK_P:format(id, data[1], DIMENSION_P:format(unpack(data, 2))))
			end
			for id, data in pairs(self.filters) do
				o:write(FILTER_P:format(id, data))
			end
			o:write("</defs>")
		end,
		write_state = function(self, o, name, base, newvalue)
			if newvalue == base then return newvalue end
			if base then o:write("</g>") end
			if newvalue then o:write(("<g class=\"%s-%s\">"):format(name, newvalue)) end
			return newvalue
		end,
		write_regions = function (self, o, regions)
			local base_layer = nil
			for _, data in ipairs(regions) do
				local layer, type, region = unpack(data)
				base_layer = self:write_state(o, "layer", base_layer, layer)
				local dimension = DIMENSION_P:format(get_dimension(region, self.height, type == 2))
				if type == 1 then
					local texture = get_texture(region)
					if texture then
						local rname, inverted = unpack(data, 4)
						dimension = DIMENSION_P:format(unpack(data, 6))
						if inverted then
							dimension = dimension..[[ transform="matrix(-1, 0, 0, 1, 0, 0)"]]
						end
						if self.masks[rname] then
							o:write(MASKED_TEXTURE_P:format(rname, rname, dimension, texture))
						else
							o:write(TEXTURE_P:format(rname, dimension, texture))
						end
					else
						o:write(RECT_P:format(dimension))
					end
				else
					local text = region.GetText and region:GetText()
					if text then
						local font, height, outline = region:GetFont()
						local left, top, width, height = get_dimension(region, self.height, true)
						font = self:encode_font(font)

						o:write(FONTSTRING_P:format(DIMENSION_P:format(left, top + height, width, height), font, height, get_color(region), encode(text)))
					end
				end
			end
			base_layer = self:write_state(o, "layer", base_layer, nil)
		end,
		add_backdrop_filters = function (self, frame, id, backdrop)
			local left, top, width, height = get_dimension(frame, self.height)
			if backdrop.insets then
				left = left + backdrop.insets.left
				top = top - backdrop.insets.top
				width = width - (backdrop.insets.left + backdrop.insets.right)
				height = height - (backdrop.insets.top + backdrop.insets.bottom)
			end
			if backdrop.bgFile then
				local texture = encode_texture(backdrop.bgFile..".png")
				local t_width, t_height = png.getsize(texture)
				local r, g, b, a = frame:GetBackdropColor()
				self.filters[id.."_bg"] = BACKDROP_FILTER_P:format(texture, t_width, t_height, r, g, b, a, DIMENSION_P:format(left, top, width, height))
			end
--~ 			if backdrop.edgeFile then
--~
--~ 			end
		end,
		write_frame_backdrop = function (self, o, f, id, left, top, width, height)
			local backdrop = f:GetBackdrop()
			if not backdrop then return end
			local insets = backdrop.insets
			if insets then
				left = left + insets.left
				width = width - (insets.left + insets.right)
				top = top - insets.top
				height = height - (insets.top + insets.bottom)
			end
			if backdrop.bgFile then
				o:write(BACKDROP_P:format(id, DIMENSION_P:format(left, top, width, height)))
			end
		end,
		write_frame = function (self, o, id, f)
			local script
			local name = f:GetName()
			if name == "UIParent" then
				script = " pointer-events=\"none\""
			elseif name then
				script = " onmouseover=\"showinfo('"..name.."')\""
			else
				script = ""
			end
			local class
			if UI.params[f].protected then
				class = "protected-frame"
			else
				class = "frame"
			end
			o:write(FRAME_HEADER_P:format(script, DIMENSION_P:format(get_dimension(f, self.height)), class))
--			o:write(("<!-- name = %s strata = %s level = %s -->"):format(tostring(f:GetName()), f:GetFrameStrata(), f:GetFrameLevel()))
			self:write_frame_backdrop(o, f, id, get_dimension(f, self.height))
			local regions = self.regions[id]
			if regions then
				table.sort(regions, sort_regions)
				self:write_regions(o, regions)
			end
			o:write(FRAME_FOOTER)
		end,
		write_frames = function (self, o)
			local base_strata = nil
			for _, data in ipairs(self.frames) do
				base_strata = self:write_state(o, "strata", base_strata, data[1])
				self:write_frame(o, unpack(data, 3))
			end
			self:write_state(o, "strata", base_strata, nil)
		end,
		write = function (self)
			print("SVG", self.file, #self.frames)
			local o = io.open(self.file, "w")
			o:write(HEADER_P:format(self.width, self.height, self.width, self.height))
			self:write_defs(o)
			o:write([[<g id="content" enable-background="new">]])
			self:write_frames(o)
			o:write([[</g>]])
			o:write(FOOTER_P:format(
				self.width / 2, self.height / 2,
				self.width / 2, self.height / 2 + 28))
			o:close()
		end,
		close = function (self)
			table.sort(self.frames, sort_frames)
			self:write()
		end,
		add_regions = function (self, layers, ...)
			local regions = {}
			for i = 1, select("#", ...) do
				local region = select(i, ...)
				local rtype = region:IsObjectType("Texture") and 1 or 2
				if region:IsShown() and get_dimension(region, self.height, rtype == 2) then
					local layer = region:GetDrawLayer()
					if layers[layer] then
						if rtype == 1 then
							local inverted
							local t = UI.params[region]
							local left, top, width, height = get_dimension(region, self.height)

							local i_width, i_height =
								width / (t.texcoordright - t.texcoordleft),
								height / (t.texcoordbottom - t.texcoordtop)
							local i_left, i_top =
								left - t.texcoordleft * i_width,
								top - t.texcoordtop * i_height
							if i_width < 0 then
								i_width = - i_width
								i_left = left - t.texcoordleft * i_width
								i_left = - (i_left + i_width * 2)
								left = left - width
								inverted = true
							end
							local texture = get_texture(region)
							if texture then
								local t_width, t_height = png.getsize(texture)
							end
							local rname = "texture"..self.index
							if t.blend ~= "DISABLE" then -- SVG doesn't support additive blend :-(
								self.masks[rname] = { texture, i_left, i_top, i_width, i_height }
							end
							self.index = self.index + 1
							regions[#regions + 1] = { layer, rtype, region, rname, inverted, i_left, i_top, i_width, i_height }
							self.paths[rname] = { left, top, width, height }
						else
							regions[#regions + 1] = { layer, rtype, region,  }
						end
					end
				end
			end
			return next(regions) and regions
		end,
		add = function (self, ...)
			for i = 1, select("#", ...) do
				local f = select(i, ...)
				local strata = assert(f:GetFrameStrata(), "No strata for "..(f:GetName() or ""))
				if f:IsShown() and get_dimension(f, self.height) and strata ~= "UNKNOWN" then
					local name = f:GetName()
					if not name then
						name = "_frame"..self.index
						self.index = self.index + 1
					end
					self.frames[#self.frames + 1] = { strata, f:GetFrameLevel(), name, f }
					self.regions[name] = self:add_regions(UI.params[f].layers, f:GetRegions()) -- FIXME ?? layers access
					local backdrop = f:GetBackdrop()
					if backdrop then
						self:add_backdrop_filters(f, name, backdrop)
					end
					self:add(f:GetChildren())
				end
			end
		end,
	},
}

function new(width, height)
	return setmetatable({width = width, height = height}, Svg)
end
